package com.byron.media.server;

import com.byron.media.server.client.MediaClient;
import com.byron.media.server.config.MediaServerConfig;
import com.byron.media.server.handlers.*;
import com.byron.media.server.model.DataFactory;
import com.byron.media.server.model.MediaData;
import com.byron.media.server.utils.IniReader;
import com.byron.media.server.utils.NetworkUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.List;

@Slf4j
public class MediaServer extends Thread {

    public static final int MTU = 1024;

    private ServerBootstrap bootstrap;

    private ChannelFuture closeFuture;

    private DataFactory dataFactory = new DataFactory();

    private SessionContainer sessionContainer;

    private MediaClient mediaClient;    // 作为代理客户端连接到上层主服务器

    private MediaServerConfig config;

    private NetworkUtils networkUtils;

    public MediaServer(MediaServerConfig config) {
        this.config = config;
        this.dataFactory = new DataFactory();
        this.sessionContainer = new SessionContainer(dataFactory);
        this.networkUtils = new NetworkUtils();
    }

    @Override
    public void run() {
        networkUtils.start();
        if(!StringUtils.isEmpty(config.getMainServer()) && config.getMainServerPort() > 0 && config.isProxyServer()){
            connectToMainServer();
        }
        startAsServer();
        networkUtils.destroy();
    }

    /**
     * 连接到主服务器
     */
    private void connectToMainServer() {
        mediaClient = new MediaClient(dataFactory, sessionContainer, config);
        mediaClient.start();

        sessionContainer.setMediaClient(mediaClient);
    }

    /**
     * 作为服务器启动
     */
    private void startAsServer(){
        int processors = 0;
        if(config.getProcess() == 0){
            processors = Runtime.getRuntime().availableProcessors();
        } else {
            processors = config.getProcess();
        }
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup(processors * 2);
        try {
            bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .option(ChannelOption.SO_RCVBUF, 500 * 1024 * 1024)
                    .option(ChannelOption.SO_SNDBUF, 500 * 1024 * 1024)
                    .option(ChannelOption.MAX_MESSAGES_PER_READ, 100)

                    // 内存池优化
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new ChunkedWriteHandler());
                            p.addLast(new MediaDecoder(dataFactory));
                            p.addLast(new MediaEncoder(dataFactory));
                            if(config.getHeart() > 0){
                                p.addLast(new MediaHeartBeat(config.getHeart()));
                            }
                            p.addLast(new MediaSession(MediaServer.this, sessionContainer));
                            p.addLast(new MediaServerHandler(sessionContainer, config));
                        }
                    });
//                    .childOption(ChannelOption.AUTO_READ, true);

            // Start the server.
            closeFuture = bootstrap.bind(config.getPort()).sync();

            log.warn("Gonsin Media Server 配置启动完成");

            // Wait until the server socket is closed.
            closeFuture.channel().closeFuture().sync();
            log.warn("Gonsin Media Server 关闭");

//            log.info("EchoServer.main end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


    public void end(){
        closeFuture.channel().pipeline().fireChannelInactive();
    }

    /**
     * 发送到组
     */
    public void sendToGroup(String group,  byte channel, byte type, long ID, byte[] data, int length){
        List<MediaData> mediaData = dataFactory.createData(channel, ID, data, length, type);
        mediaData.forEach(d -> {
            sessionContainer.sendToGroup(group, d);
        });
    }


    /**
     * 清理缓存
     * @param group
     */
    public void cleanIframe(String group){
        sessionContainer.cleanIframe(group);
    }

    public static void main(String[] args) throws IOException {

        int port = 1553;
        int heart = 0;
        String mainServer = null;      // 主机地址
        int mainServerPort = -1;      // 主机地址
        String rootPath = null;      // 本地主目录
        boolean proxyServer = false;    // 是否为代理服务器
        boolean broadcastRtp = false;    // 是否为需要组播
        int processes = 0;
        int maxMills = 10 * 1000;
        int minMills = 4 * 1000;

        // 读取配置文件
        File iniFile = new File("./media_server.ini");
        if(iniFile.exists()){
            IniReader reader = new IniReader(iniFile.getAbsolutePath());
            String portStr = reader.getValueStr("server", "port");
            if(portStr != null && !"".equals(portStr)){
                port = Integer.parseInt(portStr);
            }

            String heartStr = reader.getValueStr("server", "heart");
            if(heartStr != null && !"".equals(heartStr)){
                heart = Integer.parseInt(heartStr);
            }

            String processesStr = reader.getValueStr("server", "processes");
            if(processesStr != null && !"".equals(processesStr)){
                processes = Integer.parseInt(processesStr);
            }

            mainServer = reader.getValueStr("server", "main_server");
            if(StringUtils.isEmpty(mainServer)){
                mainServer = null;
            }

            String mainServerPortStr = reader.getValueStr("server", "main_server_port");
            if(mainServerPortStr != null && !"".equals(mainServerPortStr)){
                mainServerPort = Integer.parseInt(mainServerPortStr);
            } else {
                mainServerPort = -1;
            }

            String proxyServerStr = reader.getValueStr("server", "proxy_server");
            if(!StringUtils.isEmpty(proxyServerStr)){
                proxyServer = proxyServerStr.trim().equals("true");
            }

            rootPath = reader.getValueStr("server", "root_path");

            String broadcastRtpStr = reader.getValueStr("server", "broadcast_rtp");
            if(!StringUtils.isEmpty(broadcastRtpStr)){
                broadcastRtp = broadcastRtpStr.trim().equals("true");
            }

            String maxMillsStr = reader.getValueStr("codec", "max_mills");
            if(maxMillsStr != null && !"".equals(maxMillsStr)){
                maxMills = Integer.parseInt(maxMillsStr);
            }

            String minMillsStr = reader.getValueStr("codec", "min_mills");
            if(minMillsStr != null && !"".equals(minMillsStr)){
                minMills = Integer.parseInt(minMillsStr);
            }
        }

        MediaServerConfig config = new MediaServerConfig(
                port,
                rootPath,
                proxyServer,
                heart,
                broadcastRtp,
                mainServer,
                mainServerPort,
                processes,
                maxMills,
                minMills
        );

        new MediaServer(config).start();
    }

    /**
     * 该服务器是否为代理服务器
     */
    public boolean isProxyServer() {
        return config.isProxyServer();
    }
}
